#  daemon_launcher.rb
#
#  This file is part of myvnet.
#
#  Copyright (C) 2008  Filipe David Borba Manana,  fdmanana@gmail.com
#
#  myvnet is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  myvnet is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with myvnet.  If not, see <http://www.gnu.org/licenses/>.


require 'singleton'
require 'lib/daemon_registry'
require 'lib/daemon_dispatcher'
require 'lib/daemons/ping_daemon'
require 'lib/daemons/arp_daemon'
require 'lib/daemons/udp_echo_daemon'
require 'lib/config'
require 'lib/utils'
require 'lib/exceptions'
require 'lib/packet/sniffer'
require 'lib/packet/injector'


module Daemon
  
  # Registers daemons, starts the daemon dispatcher and a packet sniffer.
  #
  class Launcher
    include Singleton
    
    def initialize
      @conf = Config::Settings.instance
      hwinfo = Utils.if_hwinfo @conf.interface
      
      unless hwinfo
        msg = "Could not get the hardware information of the"
        msg << " network interface `#{@conf.interface}'"
        $log[:stderr].fatal msg
        exit 1
      end
      
      unless hwinfo[:hwtype] == :ethernet
        msg = "myvnet only supports Ethernet interfaces "
        msg << "or 802.11 interfaces managed by WLAN protocol"
        msg << " stacks providing Ethernet compatibility"
        $log[:stderr].fatal msg
        exit 1
      end
      
      hwaddr = hwinfo[:hwaddress]
      
      @dreg = Daemon::Registry.instance
      @ddisp = Daemon::Dispatcher.instance
      
      begin
        @packet_injector = Packet::Injector.new @conf.interface
      rescue RawSocketError => ex
        msg = "Could not create raw socket for packet injection: "
        msg << "#{ex.message}"
        $log[:stderr].fatal msg
        exit 1        
      end
                        
      begin
        @sniffer = Packet::Sniffer.new @conf.interface
      rescue SnifferError => ex
        $log[:stderr].fatal "Could not start packet sniffer: #{ex.message}"
        exit 1
      end
      
      prepare_iface           
      
      # capture any UDP packets destined to the simulated hosts
      udp_filter = ""      
      @conf.net_addresses.each do |addr|
        udp_filter << "(udp and dst host #{addr}) or "
      end
      udp_filter[-4,4] = ""
      
      @sniffer.add_filter udp_filter
      #

      # registration of daemons
      pingd = Daemon::PingDaemon.new("PingDaemon", @packet_injector, 
                                     hwaddr, *@conf.net_addresses)
      @dreg.register pingd
      @sniffer.add_filter pingd.packet_filter
      
      arpd = Daemon::ArpDaemon.new("ArpDaemon", @packet_injector,
                                   hwaddr, *@conf.net_addresses)
      @dreg.register arpd
      @sniffer.add_filter arpd.packet_filter
      
      udpechod = Daemon::UdpEchoDaemon.new("UdpEchoDaemon", @packet_injector,
                                           hwaddr, *@conf.net_addresses)
                                         
      @dreg.register udpechod
      #@sniffer.add_filter udpechod.packet_filter
      #
      
      if $log[:main].debug?
        msg = "Sniffer's full packet filter:\n"
        msg << @sniffer.filter
        $log[:main].debug msg
      end
      
    end
    
    
    def start
      
      begin
        @sniffer.each_packet do |packet|
          @ddisp.add_packet packet
        end
      rescue Interrupt
        @sniffer.stop
        @packet_injector.shutdown
        restore_iface_settings
        $log[:stdout].info "myvnet stopped."  
        exit 0
      end
    end
      
    
    private
    
    def prepare_iface
      path = '/proc/sys/net/ipv4/conf/all/send_redirects'
      res = system('echo "0" > ' + path)
      
      path = "/proc/sys/net/ipv4/conf/#{@conf.interface}/send_redirects"
      res = res && system('echo "0" > ' + path)
      
      path = "/proc/sys/net/ipv4/conf/#{@conf.interface}/forwarding"
      res = res && system('echo "0" > ' + path)
      
      unless res
        msg = "Error preparing interface `#{@conf.interface}' : #{$?}"
        $log[:stderr].fatal msg
        exit 1
      end
            
      rs = system("iptables -I OUTPUT 1 -o #{@conf.interface} -p icmp" +
                  " --icmp-type host-unreachable -j DROP")
                 
      unless res
        msg = "Error adding iptables rule : #{$?}"
        $log[:stderr].fatal msg
        exit 1
      end
      
    end
    
    
    def restore_iface_settings
      path = '/proc/sys/net/ipv4/conf/all/send_redirects'
      res = system('echo "1" > ' + path)
        
      path = "/proc/sys/net/ipv4/conf/#{@conf.interface}/send_redirects"
      res = res && system('echo "1" > ' + path)
      
      path = "/proc/sys/net/ipv4/conf/#{@conf.interface}/forwarding"
      res = res && system('echo "1" > ' + path)

      unless res
        msg = "Error preparing interface `#{@conf.interface}' : #{$?}"
        $log[:stderr].fatal msg
        exit 1
      end
      
      rs = system("iptables -D OUTPUT -o #{@conf.interface} -p icmp" +
                  " --icmp-type host-unreachable -j DROP")
                       
      unless res
        msg = "Error adding iptables rule : #{$?}"
        $log[:stderr].fatal msg
        exit 1      
      end
    
    end
    
  end
  
end
